﻿namespace  Codeplex.KinkumaFramework.Interactivity
{
    using System;
    using System.Reflection;
    using System.Windows;
    using System.Windows.Interactivity;

    /// <summary>
    /// DependencyPropertyではないプロパティへの疑似的なバインドをサポートするためのアクション
    /// </summary>
    public class DependencyPropertyProxyAction : TriggerAction<DependencyObject>
    {
        /// <summary>
        /// コントロール側からの値の設定中かどうかのフラグ
        /// </summary>
        private bool isSetting;

        /// <summary>
        /// コントロールのプロパティ
        /// </summary>
        private PropertyInfo propertyInfo;

        public static DependencyProperty ValueProperty = DependencyProperty.Register(
            "Value",
            typeof(object),
            typeof(DependencyPropertyProxyAction),
            new PropertyMetadata(
                (sender, e) =>
                {
                    ((DependencyPropertyProxyAction)sender).ValueChanged(e);
                }));

        public static DependencyProperty PropertyNameProperty = DependencyProperty.Register(
            "PropertyName",
            typeof(string),
            typeof(DependencyPropertyProxyAction),
            new PropertyMetadata(
                (sender, e) =>
                {
                    ((DependencyPropertyProxyAction)sender).UpdatePropertyInfo();
                }));

        /// <summary>
        /// 依存プロパティではないプロパティへの仲介役のためのプロパティ
        /// </summary>
        public object Value
        {
            get
            {
                return this.GetValue(ValueProperty);
            }

            set
            {
                this.SetValue(ValueProperty, value);
            }
        }

        /// <summary>
        /// コントロールのプロパティ名
        /// </summary>
        public string PropertyName
        {
            get
            {
                return (string)this.GetValue(PropertyNameProperty);
            }

            set
            {
                this.SetValue(PropertyNameProperty, value);
            }
        }

        protected override void OnAttached()
        {
            base.OnAttached();
            this.UpdatePropertyInfo();
        }

        private void UpdatePropertyInfo()
        {
            if (this.AssociatedObject == null)
            {
                return;
            }
            if (string.IsNullOrEmpty(this.PropertyName))
            {
                return;
            }
            this.propertyInfo = this.AssociatedObject.GetType().GetProperty(this.PropertyName);
        }

        protected override void Invoke(object parameter)
        {
            if (this.AssociatedObject == null)
            {
                return;
            }

            // コントロールからの値の設定中
            this.isSetting = true;
            try
            {
                // コントロールから値を取得してValueプロパティへ設定
                this.Value = GetTargetValue();
            }
            finally
            {
                this.isSetting = false;
            }

        }

        /// <summary>
        /// コントロールへ値を設定する。
        /// </summary>
        /// <param name="value">設定する値</param>
        private void SetTargetValue(object value)
        {
            if (this.propertyInfo == null)
            {
                throw new InvalidOperationException("PropertyNameを指定してください");
            }

            if (!this.propertyInfo.CanWrite)
            {
                throw new InvalidOperationException(this.PropertyName + "は書き込みできません");
            }

            this.propertyInfo.SetValue(this.AssociatedObject, value, null);
        }

        /// <summary>
        /// コントロールから値を取得する
        /// </summary>
        /// <returns></returns>
        protected object GetTargetValue()
        {
            if (this.propertyInfo == null)
            {
                throw new InvalidOperationException("PropertyNameを指定してください");
            }

            if (!this.propertyInfo.CanRead)
            {
                throw new InvalidOperationException(this.PropertyName + "は読み込めません");
            }

            return this.propertyInfo.GetValue(this.AssociatedObject, null);
        }

        private void ValueChanged(DependencyPropertyChangedEventArgs e)
        {
            if (this.AssociatedObject == null)
            {
                return;
            }

            // 現在コントロールからの値の設定中である場合は何もしない。
            if (this.isSetting)
            {
                return;
            }

            // バインドのソースからの値の設定の場合はコントロールへ新しい値を設定する
            this.SetTargetValue(e.NewValue);
        }
    }
}
